﻿namespace CodeDDD.Application.Framework
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using CodeDDD.Application.Seedwork;
    using CodeDDD.Domain.Seedwork;
    using CodeDDD.Infrastructure.Crosscutting.Seedwork.Validator;

    /// <summary>
    /// Basic abstract ICrudAppService implementation
    /// </summary>
    /// <typeparam name="TDto"></typeparam>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TIdentity"></typeparam>
    public abstract class CrudAppService<TDto, TEntity, TIdentity> : CrudAppService<TDto, TDto, TEntity, TIdentity>, ICrudAppService<TDto, TIdentity>
        where TDto : class, new()
        where TEntity : Entity<TIdentity>
    {
        public CrudAppService(IRepository<TEntity, TIdentity> repository)
            : base(repository)
        {
        }
    }

    /// <summary>
    /// Basic abstract ICrudAppService implementation
    /// </summary>
    /// <typeparam name="TCommandDto"></typeparam>
    /// <typeparam name="TQueryDto"></typeparam>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TIdentity"></typeparam>
    public abstract class CrudAppService<TCommandDto, TQueryDto, TEntity, TIdentity> : ICrudAppService<TCommandDto, TQueryDto, TIdentity>
        where TCommandDto : class, new()
        where TQueryDto : class, new()
        where TEntity : Entity<TIdentity>
    {
        #region Constructor

        public CrudAppService(IRepository<TEntity, TIdentity> repository)
        {
            if (repository == null)
                throw new ArgumentNullException("repository");

            this.repository = repository;
        }

        #endregion

        #region Members

        protected IRepository<TEntity, TIdentity> repository;

        protected IUnitOfWork unitOfWork
        {
            get
            {
                return repository.UnitOfWork;
            }
        }

        #endregion

        #region Protected Methods

        protected abstract TEntity MaterializeEntityFromDto(TCommandDto dto);

        protected virtual void Validate(TEntity item)
        {
            var validator = EntityValidatorFactory.GetValidator();

            if (!validator.IsValid(item))
                throw new ApplicationValidationErrorsException(validator.GetInvalidMessages<TEntity>(item));
        }

        protected virtual void Save(TEntity item)
        {
            // Validate the item
            this.Validate(item);

            unitOfWork.BeginWork();

            if (item.IsTransient())
            {
                repository.Add(item);
            }
            else
            {
                repository.Update(item);
            }

            unitOfWork.Commit();
        }

        #endregion

        #region ICrudAppService Members

        public virtual void Add(TCommandDto dto)
        {
            //if (dto == null)
            //    throw new ArgumentNullException(Messages.warning_CannotAddCustomerWithEmptyInformation);

            var item = MaterializeEntityFromDto(dto);

            this.Save(item);
        }

        public virtual void Update(TCommandDto dto)
        {
            //if (dto == null || dto.Id < 1)
            //    throw new ArgumentException(Messages.warning_CannotUpdateCustomerWithEmptyInformation);

            var item = MaterializeEntityFromDto(dto);

            this.Save(item);
        }

        public virtual void Remove(TIdentity id)
        {
            var item = repository.Get(id);

            if (item != null)
            {
                unitOfWork.BeginWork();

                repository.Remove(item);

                unitOfWork.Commit();
            }
            //else
            //    LoggerFactory.CreateLog().LogWarning(Messages.warning_CannotRemoveNonExistingCustomer);
        }

        public virtual TQueryDto Get(TIdentity id)
        {
            var item = repository.Get(id);

            if (item != null)
            {
                return item.ProjectedAs<TQueryDto>();
            }
            else
                return null;
        }

        public virtual IEnumerable<TQueryDto> GetAll()
        {
            var items = repository.GetAll();

            if (items != null && items.Any())
            {
                return items.ProjectedAsCollection<TQueryDto>();
            }
            else
                return null;
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// <see cref="M:System.IDisposable.Dispose"/>
        /// </summary>
        public void Dispose()
        {
            repository.Dispose();
        }

        #endregion
    }
}
